/*
 * DynamicTree.java
 *
 * Written by Joseph Bowbeer and released to the public domain,
 * as explained at http://creativecommons.org/licenses/publicdomain
 */

package jozart.dynamictree;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

import jozart.swingutils.Blip;
import jozart.swingutils.SwingWorker;

/**
 * Demonstrates the use of a SwingWorker thread to dynamically
 * expand the nodes of a JTree.
 * http://java.sun.com/products/jfc/tsc/articles/threads/threads3.html
 *
 * @author  Joseph Bowbeer
 * @version 2.0
 */
public class DynamicTree extends JApplet {

    private JScrollPane jScrollPane;
    private JTree jTree;
    private JPanel jPanel;
    private JLabel jlblStatus;
    private Blip blip;

    /** Creates children for expanded nodes. */
    private transient TreeNodeFactory factory;

    /** The active worker. */
    private transient SwingWorker<DefaultMutableTreeNode[]> worker;

    /** Initializes the form and sets a default source. */
    public DynamicTree() {
        initComponents();
        setSource(null);
    }

    /**
     * Initializes the tree model given a source param. Override
     * <tt>createRoot</tt> to create a root node and factory appropriate
     * for the source.
     * <p>
     * This implementation installs an artificially slow node factory.
     * The <tt>source</tt> param is ignored. 
     * 
     * @param source param used to create root node
     */
    public void setSource(Object source) {
        stopWorker();
        DefaultMutableTreeNode root = createRoot(source);
        DefaultTreeModel model = (DefaultTreeModel) jTree.getModel();
        model.setRoot(root);

        /*
         * Since nodes are added dynamically in this application, the only true
         * leaf nodes are nodes that don't allow children to be added. (By
         * default, askAllowsChildren is false and all nodes without children
         * are considered to be leaves.)
         * 
         * But there's a complication: when the tree structure changes, JTree
         * pre-expands the root node unless it's a leaf. To avoid having the
         * root pre-expanded, we set askAllowsChildren *after* assigning the
         * new root.
         */

        model.setAsksAllowsChildren(true);
    }

    /**
     * Stops the applet, including any active worker. Called on a system thread.
     */
    public void stop() {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                stopWorker();
            }
        });
    }

    /**
     * Called from within the constructor to initialize the form.
     */
    private void initComponents() {
        jScrollPane = new JScrollPane();
        jTree = new JTree();
        jPanel = new JPanel();
        jlblStatus = new JLabel();
        blip = new Blip();
        
        jScrollPane.setPreferredSize(new Dimension(320, 240));
        
        jTree.addTreeExpansionListener(new TreeExpansionListener() {
            public void treeExpanded(TreeExpansionEvent evt) {
                jTreeTreeExpanded(evt);
            }
            public void treeCollapsed(TreeExpansionEvent evt) {
                jTreeTreeCollapsed(evt);
            }
        });
        jTree.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent evt) {
                jTreeValueChanged(evt);
            }
        });
        jScrollPane.setViewportView(jTree);

        getContentPane().add(jScrollPane, BorderLayout.CENTER);

        jPanel.setLayout(new BorderLayout());

        jlblStatus.setBorder(new CompoundBorder(
            new CompoundBorder(
                new EmptyBorder(new Insets(1, 1, 1, 1)),
                new LineBorder(Color.GRAY)),
            new EmptyBorder(new Insets(0, 2, 0, 0))));
        jlblStatus.setText("Idle");
        jlblStatus.setToolTipText("Status");
        jPanel.add(jlblStatus, BorderLayout.CENTER);

        blip.setPreferredSize(new Dimension(23, 23));
        blip.setBorder(new CompoundBorder(
            new CompoundBorder(
                new EmptyBorder(new Insets(1, 1, 1, 1)),
                new LineBorder(Color.GRAY)),
            new EmptyBorder(new Insets(1, 1, 1, 1))));
        blip.setToolTipText("Activity Indicator");
        blip.setForeground(new Color(153, 153, 204));
        blip.setOpaque(true);
        jPanel.add(blip, BorderLayout.EAST);

        getContentPane().add(jPanel, BorderLayout.SOUTH);
    }

    /**
     * Called when a node is expanded. Stops the active worker, if any, and
     * starts a new worker to create children for the expanded node.
     */
    private void jTreeTreeExpanded(TreeExpansionEvent evt) {
        stopWorker();
        DefaultMutableTreeNode node =
            (DefaultMutableTreeNode) evt.getPath().getLastPathComponent();
        if (factory != null) {
            startWorker(factory, node);
        }
    }

    /**
     * Called when a node is collapsed. Stops the active worker, if any, and
     * removes all the children.
     */
    private void jTreeTreeCollapsed(TreeExpansionEvent evt) {
        stopWorker();
        DefaultMutableTreeNode node =
            (DefaultMutableTreeNode) evt.getPath().getLastPathComponent();
        node.removeAllChildren();
        DefaultTreeModel model = (DefaultTreeModel) jTree.getModel();

        /*
         * To avoid having JTree re-expand the root node, we disable
         * ask-allows-children when we notify JTree about the new node
         * structure.
         */

        model.setAsksAllowsChildren(false);
        model.nodeStructureChanged(node);
        model.setAsksAllowsChildren(true);

        jlblStatus.setText("Collapsed "+node);
    }

    /** Updates the status line when a node is selected. */
    private void jTreeValueChanged(TreeSelectionEvent evt) {
        Object node = evt.getPath().getLastPathComponent();
        String s = evt.isAddedPath() ? "Selected "+node : "";
        jlblStatus.setText(s);
    }

    /**
     * Given a node factory and an expanded node, starts a SwingWorker to create
     * children for the expanded node and insert them into the tree.
     */
    protected void startWorker(final TreeNodeFactory fac,
            final DefaultMutableTreeNode node) {
        final Object userObject = node.getUserObject();

        worker = new SwingWorker<DefaultMutableTreeNode[]>() {
            protected DefaultMutableTreeNode[] construct() throws Exception {
                /* Create children for the expanded node. */
                return fac.createChildren(userObject);
            }
            protected void finished() {
                /*
                 * Set the worker to null and stop the animation, but only if we
                 * are the active worker.
                 */
                if (worker == this) {
                    worker = null;
                    blip.stop();
                }
                try {
                    /*
                     * Get the children created by the factory and insert them
                     * into the local tree model.
                     */
                    DefaultMutableTreeNode[] children = get();
                    for (int i = 0; i < children.length; i++) {
                        node.insert(children[i], i);
                    }
                    DefaultTreeModel model =
                        (DefaultTreeModel) jTree.getModel();
                    model.nodeStructureChanged(node);
                    jlblStatus.setText("Expanded "+node);
                } catch (CancellationException ex) {
                    jlblStatus.setText("Failed expanding "+node+": cancelled");
                } catch (ExecutionException ex) {
                    /* Handle exceptions thrown by the factory method. */
                    Throwable err = ex.getCause();
                    jlblStatus.setText("Failed expanding "+node+": "+err);
                } catch (InterruptedException ex) {
                    // event-dispatch thread won't be interrupted
                    throw new IllegalStateException(ex + "");
                }
            }
        };

        /* Start worker, update status line, and start animation. */
        worker.start();
        jlblStatus.setText("Expanding "+node+"...");
        blip.start();
    }

    /** Stops the active worker, if any. */
    protected void stopWorker() {
        if (worker != null) {
            worker.cancel(true);
            // worker set to null in finished
        }
    }

    /**
     * Creates a root node for the local tree model and sets
     * the factory that will to be used to create the children.
     * <p>
     * This implementation creates a slow default factory. The
     * <tt>source</tt> param is ignored.
     * 
     * @param source param for root construction
     * @return root node
     */
    protected DefaultMutableTreeNode createRoot(Object source) {
        TreeNodeFactory fac = new DefaultTreeNodeFactory();
        factory = new SlowTreeNodeFactory(fac, 1000);
        return new DefaultMutableTreeNode("Dynamic Tree", true);
    }

//    /**
//     * Creates a root node for the local tree model and sets
//     * the factory that will to be used to create the children.
//     * <p>
//     * This implementation installs a slow file system factory.
//     * The <tt>source</tt> param is the root directory path.
//     * 
//     * @param source param for root construction
//     * @return root node
//     */
//    protected DefaultMutableTreeNode createRoot(Object source) {
//        String path = (source == null || source.equals("")) ?
//            System.getProperty("user.home") : source.toString();
//        TreeNodeFactory fac = new FileSystemNodeFactory(path);
//        factory = new SlowTreeNodeFactory(fac, 20);
//        return new DefaultMutableTreeNode(path, true);
//    }

    /**
	 * Launches this applet in a frame.
     * @param args ignored
	 */
    public static void main(String[] args) {
        final JFrame frame = new JFrame("Dynamic Tree");
        final DynamicTree applet = new DynamicTree();
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                applet.stopWorker();
                System.exit(0);
            }
        });
        frame.getContentPane().add(applet, BorderLayout.CENTER);
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                frame.pack();
                frame.setVisible(true);
                applet.init();
                applet.start();
            }
        });
    }
}
